package leeon.mobile.server.bbscache;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import leeon.mobile.BBSBrowser.NetworkException;
import leeon.mobile.BBSBrowser.actions.BBSGaeCacheAction;
import leeon.mobile.BBSBrowser.models.BoardObject;
import leeon.mobile.BBSBrowser.models.DocObject;
import leeon.mobile.server.bbscache.entity.Doc;
import leeon.mobile.server.bbscache.entity.DocMini;
import leeon.mobile.server.bbscache.entity.OffsetIndex;

/**
 * 收集bbs缓存的服务，该类中方法被定时调用，收集缓存
 * @author leeon
 */
public class BBSCacheService {
	
	private static final Logger logger = Logger.getLogger(BBSCacheService.class.getName());
	
	//刷新缓存最大时间
	private static final int REFRESH_CACHE_MAX_TIME = 12*60*60*1000;//12小时
	//删除缓存最大时间
	private static final int DELETE_CACHE_MAX_TIME = 3*24*60*60*1000;//3天
	//获取的最大页数
	private static final int FETCH_MAX_COUNT = 2000;//2000贴
	//每次任务获取的最大页数
	private static final int FETCH_MAX_COUNT_QUEUE = 10;//10贴
	
	//提取url的实例
	private BBSGaeCacheAction action = new BBSGaeCacheAction();
		
	//刷新十大
	public List<DocObject> refreshTopTen(boolean onlyTopTen) {
		logger.info("start refresh top ten");
		try {
			//获取十大帖子列表
			List<DocObject> topTen = action.topTenDocList();
			
			if (!onlyTopTen) {
				//循环查看每一个十大贴的主题，在数据库是否已经存在。
				for (DocObject doc : topTen) {
					//替换成缓存里有bid的版面
					doc.setBoard(Utils.findBoard(doc.getBoard().getName()));
					fetchAndSaveDocByGID(doc);					
				}
			}
			return topTen;
		} catch (NetworkException e) {
			logger.log(Level.SEVERE, "fetch all board error", e);
		}
		logger.info("end refresh top ten");
		return null;
	}
	
	//获取新的文章,返回更新的文章数,但并不是新增的文章数
	public void refreshNewDoc(BoardObject board) {
		logger.info("start refresh board:[" + board.getName() +":"+ board.getId() +"]");
		
		//暂时只能采用白名单，所以不对没有id的版面处理
		boolean noId = (board.getId() == null);
		if (noId) {
			logger.info("this board no id, don't support");
			return;
		}
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		//数据库fetch该版面文章中，最新的一篇,拿到发布日期
		Date fromDate = new Date(0);
		Date nowDate = new Date();
		try {
			//modify 为了尽量少的建立索引，改为用ID进行比较
			//modify 改为使用人工索引，减少key desc的索引
			//第一次如果找到的值不对，那么在进行一次重新创建索引的搜索
			//如果第一次启动，数据库0记录，执行两次
			//如果第一次启动，数据库有记录，执行一次
			//非一次启动，数据库有记录，应该有缓存，可能缓存有问题，最多执行两次
			int ok = 2;
			boolean alwaysCreate = false;
			while (ok-- > 0) {
				OffsetIndex index = CacheService.getOrCreateIndex(board, alwaysCreate);
				Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " +
					"key >= bidParam && key < bidParamM " +
					"parameters Long bidParam, Long bidParamM");
				query.setRange(0, 2);
				@SuppressWarnings("unchecked")
				List<Doc> results = (List<Doc>) query.executeWithArray(
						Doc.creatBoardFilter(Long.valueOf(board.getId()), index.last()));
				if (results != null && results.size() == 1) {//只可能等于1，等于2或者0都有问题
					fromDate = results.get(0).getDate();
					ok = 0;
				} else {
					alwaysCreate = true;
				}
			}
		} finally {
			pm.close();
		}
		
		//提取从发布日期到现在，且距离现在不超过缓存最大时间的所有帖子
		if (nowDate.getTime() - fromDate.getTime() > REFRESH_CACHE_MAX_TIME) {
			fromDate = new Date(nowDate.getTime()-REFRESH_CACHE_MAX_TIME);
		}
		logger.info("refresh board:[" + board.getName() + "], from date:[" + Utils.dateToString(fromDate) + "]");

		if (CacheService.getNewDocCache(board) != null) {
			logger.warning("other new doc queue is still cache, can't refresh new doc queue:[" + board.getName() +"]");
			return;
		}
		
		//根据每一个帖子，提取帖子所在主题的所有帖子
		int totalCount = 0;//已经获取的总贴数
		int stickyCount = 0;//置底的数量
		List<DocMini> list = new ArrayList<DocMini>();//最后需要保存的doc集合
		//循环置获取值的上限
		while(totalCount < FETCH_MAX_COUNT) {
			//循环获取文章列表
			try {
				//获取文章列表
				List<DocObject> list1 = action.boardDoc(board, false, totalCount, stickyCount);
				//循环获取到的列表，并加入到总列表中
				for (int i = list1.size()-1; i >= 0; i --) {
					//以下的判断是不让置底贴重复加入
					if (list.size() < 20 || !list1.get(i).isSticky()) {
						list.add(0, DocMini.toDocMini(list1.get(i)));//modify 让list顺序成为正序
						if (list1.get(i).isSticky()) stickyCount++;
					}
				}
			} catch (NetworkException e) {
				logger.log(Level.SEVERE, "fetch board doc error" + board.getName(), e);
			}
			
			//判断最旧一篇是否早于fromDate
			if (list.get(0).getDate().getTime() < fromDate.getTime()) {
				logger.info("refresh board:[" + board.getName() + "], end fetch from date, and doc number:["+list.size()+"]");
				break;
			} else {
				totalCount = list.size();
			}
		}
		
		//没有初始化的id，那么保存id
		//暂时只能采用白名单，所以不对没有id的版面处理
		//if (noId) CacheService.saveAllForBoard(board, "id");
		
		//利用queue提取
		addDocToQueue(board, fromDate, list, new ArrayList<DocMini>());
	}	
	
	
	private void addDocToQueue(BoardObject board, Date fromDate, List<DocMini> list, List<DocMini> saving) {
		//需要保存到缓存中的状态包括list, savinglist, fromdate, boardobject
		CacheService.setNewDocCache(board, list, saving);
		//加入下一次queue进行执行
		QueueService.addNewQueue(board, fromDate);
		logger.info("not end refresh board:[" + board.getName() + ":" + Utils.dateToString(fromDate) + "], save status object in cache, add task to queue");
	}
		
	/**
	 * 用于在queue中重复调用，以保证每次调用的时间都小于30s
	 */
	private Map<String, String> picUrlCache;//用于pic的判断是否已经进过的缓存，每次queue的创建，结束时销毁
	public	void refreshNewDocQueue(BoardObject board, Date fromDate, List<DocMini> list, List<DocMini> saving) {
		//时间计算器
		//该计数器减到0的时候，保存各种参数到缓存，将调用放入queue，并退出该方法，
		int q = FETCH_MAX_COUNT_QUEUE;
		picUrlCache = new HashMap<String, String>();
				
		//遍历获取到的doc对象，寻找每一篇的gid
		Iterator<DocMini> i = list.iterator();
		while (i.hasNext()) {
			DocMini doc = i.next();
			//移除已经遍历过的doc对象，
			i.remove();
			
			//超过fromDate
			//it cause could not fetch stikcy
			if (doc.getDate().getTime() < fromDate.getTime()) continue;
			
			//当前对象已经存在saving，可能属于某个主题的回文，已经获取了
			if (saving.indexOf(doc) != -1) {
				logger.fine("refresh board:[" + board.getName() + "], doc exists in saving list:["+doc.getId()+"]");
				continue;
			}

			//当前对象在数据库是否存在，如果存在不提取
			if (existDocByFID(doc.toDocObject(board))) {
				logger.fine("refresh board:[" + board.getName() + "], doc exists in db:["+doc.getId()+"]");
				continue;
			}
			logger.fine("refresh board:[" + board.getName() + "], doc not exists:["+doc.getId()+"]");
			
			try {
				//获取该贴的内容，为了能够拿到gid
				List<DocObject> l = action.docContent(doc.toDocObject(board), false);
				if (l.size() == 0) continue;
				
				//置底的帖子不按主题获取
				//modify not fetch sticky
				if (doc.isSticky()) {
					//l.get(0).setGid("0");
					//saveDoc(l, board);
					//saving.add(doc);
					//logger.fine("refresh board:[" + board.getName() + "], fetching as sticky doc:["+doc.getId()+"]");
				} else {
					doc.setId(Long.valueOf(l.get(0).getGid()));
					saving.addAll(fetchAndSaveDocByGID(doc.toDocObject(board)));
					logger.fine("refresh board:[" + board.getName() + "], fetching as common doc:["+doc.getId()+"]");
				}
			} catch (Exception e) {
				logger.log(Level.SEVERE, "fetch board doc error:[" + board.getName()+":"+doc.getId()+"]", e);
			}
			
			
			//如果计数器已经归零，将所有的状态信息保存到缓存中
			if (--q == 0) {
				addDocToQueue(board, fromDate, list, saving);
				return;
			}
		}
		//为了保证save有足够的时间，在save前再进一次queue.
		//if (q != FETCH_MAX_COUNT_QUEUE) {
		//	addDocToQueue(board, fromDate, list, saving);
		//	return;	
		//}
		//保存
		//modify 在fetch后马上保存避免缓存太大
		//saveDoc(saving, board);

		//清空缓存
		CacheService.clearNewDocCache(board);
		//同步board的缓存到数据库
		BBSBoardService.synchCacheToDB(board);
		logger.info("end refresh board:[" + board.getName() + "], saving doc number:[" + saving.size() + "]");
	}
	
	private void updateBoardTotalFromSaving(List<DocMini> saving, BoardObject board) {
		//根据saving list更新版面的文章数
		//如果saving中fid等于gid
		//那么totalG+1
		//total += saving.size()

		int g = 0;
		int c = 0;
		for (DocMini d : saving) {
			if (d.getId().equals(d.getGid())) g++;
			if (!d.isSticky()) c++;
		}		
		board.setTotal(board.getTotal()+c);
		board.setTotalG(board.getTotalG()+g);
		CacheService.saveTotalForCacheBoard(board);
	}

	
	//提取同主题的文章,返回保存的文章
	public List<DocMini> fetchAndSaveDocByGID(DocObject gdoc) {
		List<DocMini> ret = new ArrayList<DocMini>();
		
		if (gdoc == null) return ret;
		if (gdoc.getBoard() == null) return ret;
		
		Long fid = existDocByGID(gdoc);
		DocObject doc = new DocObject(null, null, gdoc.getBoard());
		if (fid != null) {
			//从最后一篇开始提取
			doc.setId(String.valueOf(fid));
			logger.fine("these gdoc exist, fetch gdoc from file:[" + fid + "]");			
		} else {
			//从比gid小1的文章开始提取
			doc.setId(String.valueOf(Long.valueOf(gdoc.getId())-1));
			logger.fine("these gdoc not exist, fetch all gdoc:[" + gdoc.getId() + "]");
		}
		
		
		List<DocObject> list = new ArrayList<DocObject>();
		logger.fine("start fetch g doc:[" + gdoc.getId() + "]");
		try {
			while (list.size() < FETCH_MAX_COUNT) {
				DocObject d = list.size()==0 ? doc : list.get(list.size()-1);
				List<DocObject> l = action.docContent(d, true, gdoc);
				list.addAll(l);
				if (l.size() < 20) break;
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "fetch doc by gid error[" + gdoc.getBoard().getName()+":"+gdoc.getId()+"]", e);
		}
		logger.fine("end fetch g doc:[" + gdoc.getId() + "], and doc size:["+list.size()+"]");
		
		for (DocObject d : list) {
			d.setGid(gdoc.getId());
		}
		return saveDoc(list, gdoc.getBoard());
	}
	
	//保存同一个版面的doc
	public List<DocMini> saveDoc(List<DocObject> list, BoardObject board) {
		List<DocMini> ret = new ArrayList<DocMini>();
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		OffsetIndex index = CacheService.getOrCreateIndex(board, false);//需要更新的版面索引
		try {
			for (DocObject o : list) {
				Doc d = null;
				try {
					d = new Doc(Long.valueOf(o.getId()), Long.valueOf(o.getGid()), o.getAuthor(), 
							Utils.stringToDate("yyyy年MM月dd日HH:mm:ss", o.getDate().substring(0, 19)), 
							o.getTitle(), board.getName(), Long.valueOf(board.getId()));
					d.setContentOldAndNew(o.getContent1(), o.getContent());
					QueueService.findUrlPattern(o.getContent1(), picUrlCache);
				} catch (Exception e) {
					logger.log(Level.SEVERE, "create doc object error;["+board.getName()+":"+o.getId()+"]", e);
				}
				
				if (d != null) {
					pm.makePersistent(d);
					index.addToIndex(d.getKey());
					ret.add(DocMini.toDocMini(o));
				}
			}
		} finally {
			pm.close();
			//根据更新的记录来更新版面的文章总数
			//放在finally中，保证board total的数据正确
			updateBoardTotalFromSaving(ret, board);
			//更新索引
			CacheService.setOffsetIndexCache(board, index);
		}

		return ret;
	}
	
	//保存，同上
	public List<DocMini> saveDoc(DocObject doc, BoardObject board) {
		return saveDoc(java.util.Arrays.asList(doc), board);
	}

	//删除超期的文章
	public void deleteExpiredDoc(BoardObject board) {
		logger.info("start delete expire doc board:[" + board.getName() +":"+ board.getId() +"]");
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Date nowDate = new Date();
		Long bid = Long.valueOf(board.getId());
		int c = 0;
		int g = 0;
		OffsetIndex index = CacheService.getOrCreateIndex(board, false);//需要更新的版面索引
		try {
			Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " +
					"keyG >= bidParam && keyG < bidParamM " + 
					"parameters Long bidParam, Long bidParamM order by keyG");
			@SuppressWarnings("unchecked")
			List<Doc> results = (List<Doc>) query.executeWithArray(Doc.creatBoardFilterG(bid));
			query.getFetchPlan().setFetchSize(100);
			for (Doc d : results) {
				boolean expired = (nowDate.getTime() - d.getDate().getTime() > DELETE_CACHE_MAX_TIME);
				if (!expired) break; //过期日之后的发表的主题，肯定没有过期，不用继续查
				
				//查看每一个主题的最后一篇是否过期
				Doc last = existDocByGID(d.getFid(), bid);
				if (last == null) continue;
				
				expired = (nowDate.getTime() - last.getDate().getTime() > DELETE_CACHE_MAX_TIME);
				logger.fine("delete expire doc board:[" + board.getName() + "], " +
						"max date doc:["+Utils.dateToString(last.getDate())+"], the gid:["+d.getFid()+"] " + (!expired?"not ":"") +"expired!");
				
				if (!expired) continue;
				
				//删除以d.getfid的整个主题
				Query delete = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " + 
						"keyG.contains(gidParam) parameters Long gidParam order by key desc");
				delete.getFetchPlan().setFetchSize(100);
				
				@SuppressWarnings("unchecked")
				List<Doc> list = (List<Doc>)delete.execute(Doc.createGdocFilter(bid, d.getFid()));
				logger.fine("delete expired doc by g, all g doc:["+list.size()+"]");
				for (Doc del : list) {
					Long dk = del.getKey();
					pm.deletePersistent(del);//删除实体
					index.removeFromIndex(dk);//更新索引
					c++;
				}
				g += 1;
			}
		} finally {
			pm.close();
			//回写到board对象中记录总数//finally保证更新total
			board.setTotal(board.getTotal()-c);
			board.setTotalG(board.getTotalG()-g);
			//更新索引
			CacheService.setOffsetIndexCache(board, index);
			//更新缓存
			CacheService.saveTotalForCacheBoard(board);
			//更新缓存到数据库
			BBSBoardService.synchCacheToDB(board);			
		}
		logger.info("end delete expire doc board:[" + board.getName() + "], remain doc:["+board.getTotal()+","+board.getTotalG()+"]");
	}
	
	
	//删除超期的文章
	/**
	public void deleteExpireDoc(BoardObject board) {
		logger.info("start delete expire doc board:[" + board.getName() +":"+ board.getId() +"]");
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Date nowDate = new Date();
		int ret = 0;
		int g = 0;
		try {
			//遍历数据库的所有主题帖，并找到每一个主题帖的最后一贴
			Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " +
					"key >= bidParam && key < bidParamM " +
					"parameters Long bidParam, Long bidParamM");
			query.getFetchPlan().setFetchSize(100);
				
			@SuppressWarnings("unchecked")
			List<Doc> results = (List<Doc>) query.executeWithArray(Doc.creatBoardFilter(Long.valueOf(board.getId())));
			Collections.sort(results, new Comparator<Doc>(){
				@Override
				public int compare(Doc o1, Doc o2) {
					long r = (o1.getGid() - o2.getGid());
					if (r != 0) return (int)r;
					r =  o2.getFid() - o1.getFid();
					return (int) r;
				}
			});
			
			ret = results.size();
			Iterator<Doc> iResults = results.iterator();
			//long lastGid = deleteSticky?-1:0;//0不会删除置底
			long lastGid = -1;//0不会删除置底
			boolean expire = false;
			while (iResults.hasNext()) {
				Doc o = iResults.next();
				//如果最后一贴的日期到现在超过了最大缓存日期，那么该主题帖删除
				if (o.getGid() != lastGid) {
					expire = (nowDate.getTime() - o.getDate().getTime() > DELETE_CACHE_MAX_TIME);
					logger.fine("delete expire doc board:[" + board.getName() + "], " +
						"max date doc:["+Utils.dateToString(o.getDate())+"], the gid:["+o.getGid()+"] " + (!expire?"not ":"") + "expired!");
					lastGid = o.getGid();
				}
				
				logger.fine("delete expire doc board:[" + board.getName() + "], " +
						"the id:["+o.getFid()+"] the gid:["+o.getGid()+"] " + (!expire?"not ":"") + "expired!");				
				if (!expire) {
					if (o.getGid() == 0) ret--;//不计数置底
					if (o.isGdoc()) g++;
					iResults.remove();
				} else {
					ret--;
				}
			}
			
			pm.deletePersistentAll(results);
		} finally {
			pm.close();
		}
		board.setTotal(ret);
		board.setTotalG(g);
		
		//更新缓存
		CacheService.saveTotalForCacheBoard(board);
		//更新缓存到数据库
		BBSBoardService.synchCacheToDB(board);
		logger.info("end delete expire doc board:[" + board.getName() + "], remain doc:["+ret+","+g+"]");
	}
	**/
			
	//该主题帖是否存在
	//存在返回最后一个fid,否则就是null
	public Long existDocByGID(DocObject doc) {
		if (doc == null) return null;
		if (doc.getId() == null) return null;
		if (doc.getBoard() == null) return null;
		if (doc.getBoard().getId() == null) return null;
		
		Doc d = existDocByGID(Long.valueOf(doc.getId()), Long.valueOf(doc.getBoard().getId()));
		if (d == null) return null;
		else return d.getFid();
	}
	
	//fid 为主题贴的id
	public Doc existDocByGID(Long fid, Long bid) {
		if (fid == null || bid == null) return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " +
					"keyG.contains(gidParam) " +
					"parameters Long gidParam order by key desc");
			query.setRange(0, 1);
			@SuppressWarnings("unchecked")
			
			List<Doc> results = (List<Doc>) query.execute(Doc.createGdocFilter(bid, fid));
			return (results!=null&&results.size()!=0?results.get(0):null);
		} finally {
			pm.close();
		}
	}
	
	//该贴是否存在
	public boolean existDocByFID(DocObject doc) {
		if (doc == null) return false;
		if (doc.getId() == null) return false;
		if (doc.getBoard() == null) return false;
		if (doc.getBoard().getId() == null) return false;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Long key = Doc.createKey(Long.valueOf(doc.getBoard().getId()), Long.valueOf(doc.getId()));
			Doc d = pm.getObjectById(Doc.class, key);
			return (d != null);
		} catch (JDOObjectNotFoundException e) {
			return false;
		} finally {
			pm.close();
		}
	}
	
	//删除所有贴子
	public void clearDoc() {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc");
			query.getFetchPlan().setFetchSize(100);
			query.deletePersistentAll();
		} finally {
			pm.close();
		}
	}
	
	//删除某个版面的所有帖子
	public void clearDoc(BoardObject board) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Query query = pm.newQuery("select from leeon.mobile.server.bbscache.entity.Doc where " +
					"key >= bidParam && key < bidParamM " +
					"parameters Long bidParam, Long bidParamM");
			query.getFetchPlan().setFetchSize(100);
			Object[] p = Doc.creatBoardFilter(Long.valueOf(board.getId()));
			query.deletePersistentAll(p[0], p[1]);
		} finally {
			pm.close();
		}
		board.setTotal(0);
		board.setTotalG(0);
		
		CacheService.saveTotalForCacheBoard(board);
		BBSBoardService.synchCacheToDB(board);
	}
			
	/**
	 * @param args
	 */
	public static void main(String[] args) {
	}

}
